/*
 * @Author: ipk518 121206530@qq.com
 * @Date: 2023-11-20 09:01:08
 * @LastEditors: ipk518 121206530@qq.com
 * @LastEditTime: 2024-03-06 19:53:07
 * @FilePath: /moduleTest/src/spi.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#include <fcntl.h>
// #include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include "string.h"
#include "spi.h"
#include "myLog.h"

struct _Params {
    char        dev[64];
    uint32_t    speed;
    uint64_t    size;
    uint32_t    cycle;
    uint32_t    mode;
    uint8_t     bits;
    uint16_t    delay;
};
static struct _Params params;

static int initSpiDev(const char* dev_path)
{
    int ret = 0;
    static uint32_t SPIMODE = SPI_MODE_3;	// 上升沿，极性
    static uint8_t SPIBITS = 8;
    static uint32_t MAXSPEED = 50000000;  //5000000
    
    memcpy(params.dev,dev_path,15);
    
    int g_spidevFd = open(dev_path, O_RDWR);
    if(-1 == g_spidevFd){
        qlog_e("open g_szSPIFile error");
        return -1;
    }
    //设置写入SPI模式
    /* Set spi mode */
    ret = ioctl(g_spidevFd, SPI_IOC_WR_MODE32, &params.mode);
    if(ret < 0){
        qlog_e("set SPI_IOC_WR_MODE error");
        return -1;
    }
    //设置读取SPI模式
    ret = ioctl(g_spidevFd, SPI_IOC_RD_MODE, &params.mode);
    if(ret < 0){
        qlog_e("set SPI_IOC_RD_MODE error");
        return -1;
    } 
    //设置SPI写入设备的字长
    ret = ioctl(g_spidevFd, SPI_IOC_WR_BITS_PER_WORD, &params.bits);
    if(ret < 0){
        qlog_e("set SPI_IOC_WR_BITS_PER_WORD per word error");
        return -1;
    }
    //设置SPI读出设备的字长
    ret = ioctl(g_spidevFd, SPI_IOC_RD_BITS_PER_WORD, &params.bits);
    if(ret < 0){
        qlog_e("set SPI_IOC_RD_BITS_PER_WORD per word error");
        return -1;
    }
//设置写入SPI设备的最大通信速率
   ret = ioctl(g_spidevFd,SPI_IOC_WR_MAX_SPEED_HZ,&params.speed);
   if( ret == -1){
        qlog_e("SPI_IOC_WR_MAX_SPEED_HZ error");
   }
   //设置读取SPI设备的最大通信频率
   ret = ioctl(g_spidevFd,SPI_IOC_RD_MAX_SPEED_HZ,&params.speed);
   if( ret == -1){
        qlog_e("SPI_IOC_RD_MAX_SPEED_HZ error");
   }
    qlog_i("spi mode: 0x%x", params.mode);
    qlog_i("bits per word: %d", params.bits);
    qlog_i("max speed: %d Hz (%d KHz)", params.speed, params.speed / 1000);
    return g_spidevFd;
}

static void transfer(int fd, uint8_t const *tx, uint8_t const *rx)
{
    int ret;
    struct spi_ioc_transfer tr = {
        .tx_buf         = (unsigned long)tx,
        .rx_buf         = (unsigned long)rx,
        .len            = params.size,
        .delay_usecs    = params.delay,
        .speed_hz       = params.speed,
        .bits_per_word  = params.bits,
    };

    if (params.mode & SPI_TX_QUAD)
        tr.tx_nbits = 4;
    else if (params.mode & SPI_TX_DUAL)
        tr.tx_nbits = 2;
    if (params.mode & SPI_RX_QUAD)
        tr.rx_nbits = 4;
    else if (params.mode & SPI_RX_DUAL)
        tr.rx_nbits = 2;

    if (!(params.mode & SPI_LOOP)) {
        if (params.mode & (SPI_TX_QUAD | SPI_TX_DUAL))
            tr.rx_buf = 0;
        else if (params.mode & (SPI_RX_QUAD | SPI_RX_DUAL))
            tr.tx_buf = 0;
    }

    ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    if (ret < 1) {
        perror("can't send spi message");
    }
}



static void spiSingel(int fd, uint8_t *tx_buf, uint8_t *rx_buf)
{
    uint64_t read_count = 0;
    uint64_t write_count = 0;
    struct timeval time_start, time_stop, time_diff;
    float time_cos;
    double rx_rate, tx_rate;
    uint32_t error_num = 0;
    uint32_t i = 0;

    qlog_i("spi mode: 0x%x", params.mode);
    qlog_w("cycle number:%d",params.cycle);
    /* spi transfer and calculate the total data according to the cycle of transmissions */
    gettimeofday(&time_start, NULL);
    while (params.cycle-- > 0) {
        transfer(fd, tx_buf, rx_buf);
        write_count += params.size;
        read_count += params.size;
    }
    gettimeofday(&time_stop, NULL);

    /* Calculate the time cost */
    timersub(&time_stop, &time_start, &time_diff);
    time_cos = time_diff.tv_sec * 1000000.0 + time_diff.tv_usec;

    /* qlog_i the time cost and total data to transfer */
    
    qlog_w("time : tx %.1fus, rx %.1fus", time_cos, time_cos);
    qlog_w("total: tx %.1fKB, rx %.1fKB", write_count/1024.0, read_count / 1024.0);

    /* Calculate and qlog_i the transfer speed */
    rx_rate = read_count / 1024.0 / 1024.0 / (time_cos / 1000000.0);
    tx_rate = write_count / 1024.0 / 1024.0 / (time_cos / 1000000.0);
    qlog_w("rate : tx %.3fMB/s, rx %.3fMB/s", rx_rate, tx_rate);

    /* Check the last transmission of tx/rx data */
    for (i = 0; i < params.size; i++) {
        if (rx_buf[i] != tx_buf[i])
            error_num = error_num + 1;
    }
    qlog_w("byte error rate= %.1f%%", ((float)error_num / params.size) * 100);
}


static int spiDual(int fd, uint8_t *tx_buf, uint8_t *rx_buf)
{
    uint64_t read_count = 0;
    uint64_t write_count = 0;
    struct timeval time_start, time_stop, time_diff;
    float time_cos;
    double rx_rate, tx_rate;
    uint32_t error_num = 0;
    uint32_t i = 0;
    int ret, cycle;

    qlog_i("spi mode: 0x%x", params.mode);
    cycle = params.cycle;
    qlog_w("cycle number:%d",params.cycle);
    /* single wire sends data */
    /* spi transfer and calculate the total data according to the cycle of transmissions */
    gettimeofday(&time_start, NULL);
    while (params.cycle-- > 0) {
        transfer(fd, tx_buf, NULL);
        write_count += params.size;
    }
    gettimeofday(&time_stop, NULL);

    /* Calculate the sending time cost */
    timersub(&time_stop, &time_start, &time_diff);
    time_cos = time_diff.tv_sec * 1000000.0 + time_diff.tv_usec;

    /* Printf the time cost and total data to transfer */
    qlog_w("time : tx %.1fus", time_cos);
    qlog_w("total: tx %.1fKB", write_count / 1024.0);

    /* Calculate and printf the transfer speed */
    tx_rate = write_count / 1024.0 / 1024.0 / (time_cos / 1000000.0);
    qlog_w("rate : tx %.3fMB/s", tx_rate);

    /* Change to dual wire receive data */
    params.mode |= SPI_RX_DUAL;
    qlog_w("spi mode: 0x%x", params.mode);
    ret = ioctl(fd, SPI_IOC_WR_MODE32, &params.mode);
    if (ret == -1) {
        perror("SPI_IOC_WR_MODE32");
        return -1;
    }

    params.cycle = cycle;
    /* spi transfer and calculate the total data according to the cycle of transmissions */
    gettimeofday(&time_start, NULL);
    while (params.cycle-- > 0) {
        transfer(fd, NULL, rx_buf);
        read_count += params.size;
    }
    gettimeofday(&time_stop, NULL);

    /* Calculate the cost of receiving time */
    timersub(&time_stop, &time_start, &time_diff);
    time_cos = time_diff.tv_sec * 1000000.0 + time_diff.tv_usec;

    /* Printf the time cost and total data to transfer */
    qlog_w("time : rx %.1fus", time_cos);
    qlog_w("total: rx %.1fKB", read_count / 1024.0);

    /* Calculate and printf the transfer speed */
    rx_rate = read_count / 1024.0 / 1024.0 / (time_cos / 1000000.0);
    qlog_w("rate : rx %.3fMB/s", rx_rate);

    /* Check the last transmission of tx/rx data */
    for (i = 0; i < params.size; i++) {
        if (rx_buf[i] != tx_buf[i])
            error_num = error_num + 1;
    }
    qlog_w("byte error rate= %.1f%%", ((float)error_num / params.size) * 100);

    return 0;
}


// "  # ./%s -d /dev/spidev0.0 -s 5000000 -OH -m 1 -S 2048 -c 2\n"
// "  # ./%s -d /dev/spidev0.0 -s 100000000 -OH -m 1 -S 1048576 -c 100\n"
// "  # ./%s -d /dev/spidev0.0 -s 5000000 -OH -m 2 -S 2048 -c 1\n"
void spiTest(int mode,int size,int speed)
{
    uint8_t *tx_buf = NULL, *rx_buf = NULL;
    int ret = -1;
    int fd = -1;
    int i = 0;
    params.size = size*1024;
    params.speed = speed*1000;
    params.bits = 8;
    params.cycle = 100;
    params.delay = 0;
    params.mode = SPI_MODE_3;

    fd = initSpiDev(SPI0);
    if (fd < 0) {
        perror("open");
        return;
    }

    tx_buf = malloc(params.size);
    if (tx_buf == NULL) {
        perror("tx_buf malloc");
        goto error;
    }

    rx_buf = malloc(params.size);
    if (rx_buf == NULL) {
        perror("rx_buf malloc");
        goto error;
    }

    /* Generate random numbers to sent */
    for (i = 0; i < params.size; i++) {
        tx_buf[i] = i;
    }

    if (mode == 1) {
        /* single spi write/read test */
        if (params.cycle) {
            spiSingel(fd, tx_buf, rx_buf);
        } else {
            goto error;
        }
    } else if (mode == 2) {
        /* dual spi write/read test */
        if (params.cycle) {
            ret = spiDual(fd, tx_buf, rx_buf);
            if (ret == -1) {
                goto error;
            }
        } else {
            goto error;
        }
    } else {
        goto error;
    }

error:
    if (fd != -1)
        close(fd);

    if (tx_buf != NULL)
        free(tx_buf);

    if (rx_buf != NULL)
        free(rx_buf);
}